<?php

if (!defined("__root"))
	define("__root", preg_replace("#/$#", "", $_SERVER['DOCUMENT_ROOT']));

if (!defined("__cwd"))
	define("__cwd", getcwd());

if (!defined("__tfgallery_path"))
	define("__tfgallery_path", dirname(__FILE__));

class TFGallery {

	const DEFAULT_CONF = "./config.log";
	const NAME = "TrueFusion Gallery";
	const VERSION = "2.0.0";

	static public $mimetypes = array
		(
			"jpeg" => "image/jpeg",
			"jpg" => "image/jpeg",
			"png" => "image/png",
			"gif" => "image/gif",
			"bmp" => "image/bmp",
			"svg" => "image/svg+xml"
		);
	private $configFile;
	private $debugging;
	static protected $dir_cache;
	private $hotlinking;
	static protected $img_cache;
	private $pathinfo;
	private $settings;
	static protected $space;

	function __construct($configFile = DEFAULT_CONF, $debugging = false)
		{
			$this->configFile = self::parsePath($configFile);
			$this->debugging = $debugging;

			if ($debugging)
				ini_set('display_errors', '1');

			// For some reason single quotes in URLs return as question marks,
			//	therefore requiring to be replaced to avoid problems.
			$this->pathinfo = str_replace("?", "'", rawurldecode(substr($_SERVER['PATH_INFO'], 1 + strlen(self::$space))));
			$this->parseConfig();

			if ($this->getCurrPath() != "/")
				$this->updateCache();
		}

	protected function displayImageInfo($image_src)
		{
			if ($this->getSetting("show_image_info"))
			{
				list($width, $height, $type, $attr, $bits, $channels, $mime) = array_values(getimagesize($image_src));
				echo "\t\t\t<ul class=\"tfgallery_image_info\">\n";

				if ($this->getSetting("show_filenames"))
					echo "\t\t\t\t<li><b>File name:</b> ".basename($image_src)."</li>\n";

				if ($this->getSetting("show_mimetype"))
					echo "\t\t\t\t<li><b>Mimetype:</b> ".$mime."</li>\n";

				if ($this->getSetting("show_width"))
					echo "\t\t\t\t<li><b>Width:</b> ".$width."px</li>\n";

				if ($this->getSetting("show_height"))
					echo "\t\t\t\t<li><b>Height:</b> ".$height."px</li>\n";

				if ($this->getSetting("show_filesizes"))
					echo "\t\t\t\t<li><b>File size:</b> ".round(filesize($image_src)/1024, 2)." kb</li>\n";

				echo "\t\t\t</ul>\n";
			}
		}

	protected function filterPath($path, $type)
		{
			$path = realpath($path);
			$dirListing = array_diff(scandir($path), array(".", "..", "thumbs"));
			$exts = array_keys(self::$mimetypes);

			foreach ($dirListing as $value)
			{
				$info = pathinfo($path.DIRECTORY_SEPARATOR.$value);

				if (is_dir($path.DIRECTORY_SEPARATOR.$value))
					$dirs[] = $path.DIRECTORY_SEPARATOR.$value;

				else if (is_file($path.DIRECTORY_SEPARATOR.$value)
				  && in_array($info['extension'], $exts))
					$files[] = $path.DIRECTORY_SEPARATOR.$value;
			}

			if ($type == "dirs")
				return $dirs;
			else if ($type == "files")
				return $files;
			else if ($type == "both")
				return array('dirs' => $dirs, 'files' => $files);
		}

	/*!	\brief Render the gallery.
	 *
	 *	@param string $layout Possible values: 'table' or 'list'
	 */
	function gallery($layout = "table")
		{
			/*!	\TODO
			 *
			 *	Implement $limit
			 */
			if (!$this->isPathInfoFile())
			{
				$cols = $this->getSetting("columns");
				$files = self::$img_cache;
				$count = count($files);
				$limit = $this->getSetting("limit");
				$page = (int) $_GET['page'];
				$page = (--$page < 0) ? 0 : $page;

				if ($count > 0)
				{
					$files = array_slice($files, $limit * $page, $limit);
					$count = count($files);
					$rows = $count / $cols;

					if ($layout == "table")
						echo "<table class=\"tfgallery_body\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n";

					for ($y = 0; $y < $rows; $y++)
					{
						if ($layout == "list")
							echo "<ul class=\"tfgallery_body\">\n";

						if ($layout == "table")
							echo "\t<tr>\n";

						for ($x = 0; $x < $cols; $x++)
						{
							if (empty($files[$y * $cols + $x]))
								break;

							$thumb_attr = $this->getImageAttributes($files[$y * $cols + $x], true);

							if ($layout == "table") echo "\t\t<td>\n";
							if ($layout == "list") echo "\t<li>\n";

							echo "\t\t\t<a href=\"".$this->parseImageLink($files[$y * $cols + $x])."\" style=\"width: ".$thumb_attr['width']."px; height: ".$thumb_attr['height']."px;\">";
							echo "<img src=\"".$this->genThumbPath($files[$y * $cols + $x])."\" ".$thumb_attr['attr']."/>";
							echo "</a>\n";

							$this->displayImageInfo($files[$y * $cols + $x]);

							if ($layout == "table") echo "\t\t</td>\n";
							if ($layout == "list") echo "\t</li>\n";
						}

						if ($layout == "table")
							echo "\t</tr>\n";

						if ($layout == "list")
							echo "</ul>\n";
					}
					if ($layout == "table") echo "</table>\n";
				}

				else $this->renderRootCategories();
			}

			else
			{
				$count = count(self::$img_cache);
				$curr_img_path = $this->getCurrentImagePath();
				$curr_key = array_search($curr_img_path, self::$img_cache);

				$path = $this->getSetting("path");
				$path .= DIRECTORY_SEPARATOR.$this->pathinfo;
				$attr = $this->getImageAttributes($path);

				$next = (bool) (($curr_key+1) < $count);
				if ($next)
					echo "<a href=\"".$this->parseImageLink(self::$img_cache[$curr_key+1])."\" title=\"Move on to next image.\">";

				echo "<img id=\"tfg_current_img\" src=\"".$path = str_replace($_SERVER['DOCUMENT_ROOT'], "", $path)."\" ".$attr['attr']."/>";

				if ($next)
					echo "</a>\n";
				else
					echo "\n";
			}
		}

	protected function genThumbPath($path, $complete = false)
		{
			if (preg_match("#^".$_SERVER['DOCUMENT_ROOT']."#", $path))
			{
				if (file_exists(dirname($path).DIRECTORY_SEPARATOR."thumbs".DIRECTORY_SEPARATOR.basename($path)))
				{
					if (!$complete)
					{
						$path = str_replace($_SERVER['DOCUMENT_ROOT'], "", $path);
						return dirname($path)."/thumbs/".basename($path);
					}

					return dirname($path).DIRECTORY_SEPARATOR."thumbs".DIRECTORY_SEPARATOR.basename($path);
				}

				else
				{
					if (!$complete)
						return $this->parseIndexDir()."/images/image_placeholder.png";

					return __cwd.DIRECTORY_SEPARATOR."images".DIRECTORY_SEPARATOR."image_placeholder.png";
				}
			}
		}

	protected function getCurrPath()
		{
			$gallery_path = self::parsePath($this->getSetting("path"));
			if (!empty($this->pathinfo))
			{
				$path = $gallery_path.DIRECTORY_SEPARATOR.$this->pathinfo;
				if (!strstr($this->pathinfo, ".."))
				{
					if (is_dir($path)) return $path;
					else return dirname($path);
				}

				else {
					trigger_error("Going up? I don't think so...", E_USER_ERROR);
					exit(1);
				}
			}
			return $gallery_path;
		}

	function getPathInfo()
		{
			return $this->pathinfo;
		}

	protected function parseConfig()
		{
			$file = file($this->configFile);
			if (is_array($file))
			{
				$file = array_map("rtrim", $file);
				foreach ($file as $val)
				{
					$val = preg_replace("#\s#", "", $val);
					list($name, $value) = explode("=", $val);
					$this->settings[strtolower($name)] = str_replace('"', "", $value);
					$this->settings[strtolower($name)] = preg_replace("#[\\/]$#", "", $value);
				}
			}
		}

	protected function getCurrentImagePath()
		{
			$path = realpath($this->getCurrPath());
			$pathinfo = pathinfo($this->pathinfo);
			return $path.DIRECTORY_SEPARATOR.$pathinfo['basename'];
		}

	function getReservedString()
		{
			if (strlen(self::$space))
				return "/".self::$space;
		}

	protected function getSetting($key)
		{
			$key = strtolower($key);
			switch ($key)
			{
				case "columns":
					if (!is_numeric($this->settings[$key]))
					{
						if ($this->debugging)
							trigger_error("The amount of columns could not be determine, defaulting to 4");
						return 4;
					}
					return (int) $this->settings[$key];

				case "path":
					if (empty($this->settings[$key]))
					{
						if ($this->debugging)
							trigger_error("The images directory path could not be determined, defaulting to ".__tfgallery_path.DIRECTORY_SEPARATOR."images");
						return __tfgallery_path.DIRECTORY_SEPARATOR."images";
					}
					return realpath(self::parsePath($this->settings[$key]));

				case "limit":
					if (!is_numeric($this->settings[$key]))
					{
						if ($this->debugging)
							trigger_error("The amount of images per page could not be determined, defaulting to 20");
						return 20;
					}

					else if ($this->settings[$key] < 1)
					{
						if ($this->debugging)
							trigger_error("The amount of pages per page must be greater than 0; defaulting to 20");
						return 20;
					}
					return (int) $this->settings[$key];

				case "show_category_icons":
					if (!self::is_bool($this->settings[$key]))
					{
						if ($this->debugging)
							trigger_error("Do you want to show a random image as an icon for a category? Can't tell, defaulting to false");
						return false;
					}
					return self::toBool($this->settings[$key]);

				case "show_image_info":
					if (!self::is_bool($this->settings[$key]))
					{
						if ($this->debugging)
							trigger_error("Do you want to show the images' info? Can't tell, defaulting to false");
						return false;
					}
					return self::toBool($this->settings[$key]);

				case "show_filenames":
					if (!self::is_bool($this->settings[$key]))
					{
						if ($this->debugging)
							trigger_error("Do you want to show image file names? Can't tell, defaulting to false");
						return false;
					}
					return self::toBool($this->settings[$key]);

				case "show_width":
					if (!self::is_bool($this->settings[$key]))
					{
						if ($this->debugging)
							trigger_error("Do you want to show the width of the image? Can't tell, defaulting to false");
						return false;
					}
					return self::toBool($this->settings[$key]);

				case "show_filesizes":
					if (!self::is_bool($this->settings[$key]))
					{
						if ($this->debugging)
							trigger_error("Do you want to show image file sizes? Can't tell, defaulting to false.");
						return false;
					}
					return self::toBool($this->settings[$key]);

				case "show_height":
					if (!self::is_bool($this->settings[$key]))
					{
						if ($this->debugging)
							trigger_error("Do you want to show the height of the image? Can't tell, defaulting to false");
						return false;
					}
					return self::toBool($this->settings[$key]);

				case "show_mimetype":
					if (!self::is_bool($this->settings[$key]))
					{
						if ($this->debugging)
							trigger_error("Do you want to show the mimetype of the image? Can't tell, defaulting to false");
						return false;
					}
					return self::toBool($this->settings[$key]);

				case "ucwords":
					if (!self::is_bool($this->settings[$key]))
					{
						if ($this->debugging)
							trigger_error("Do you want to capitalize the first letter of the name of the categories? Can't tell, defaulting to true");
						return true;
					}
					return self::toBool($this->settings[$key]);
			}
		}

	function renderCategories($dirs, $show_active = true, $cols = 1)
		{
			if ($cols < 1)
				$cols = 1;

			$count = count($dirs);
			$rows = $count / $cols;

			if (is_array($dirs) && $count > 0)
			{
				if ($cols == 1)
					echo "<ul class=\"tfgallery_categories\">\n";
				else if ($cols > 1)
					echo "<table class=\"tfgallery_categories\">\n";

				for ($y = 0; $y < $rows; ++$y)
				{
					$btag = ($cols > 1) ? "\t\t<td>\n\t" : "\t<li>\n";
					$etag = ($cols > 1) ? "\t\t</td>\n" : "\t</li>\n";

					if ($cols > 1)
						echo "\t<tr>\n";

					for ($x = 0; $x < $cols; ++$x)
					{
						if (empty($dirs[$y * $cols + $x]))
							break;

						$basename[0] = rawurlencode(basename($dirs[$y * $cols + $x]));

						if ($this->splitPathInfo(0) == $basename[0] && !$show_active)
							continue;

						$basename[1] = ($this->getSetting("ucwords")) ? ucwords(basename($dirs[$y * $cols + $x])): $basename[0];

						echo $btag;
						echo "\t\t<a href=\"".$_SERVER['SCRIPT_NAME'].$this->getReservedString()."/".$basename[0]."\">";

						if ($this->getSetting("show_category_icons"))
						{
							$images = $this->filterPath($dirs[$y * $cols + $x], "files");
							if (count($images))
							{
								$rand = array_rand($images);
								echo "<img src=\"".$this->genThumbPath($images[$rand])."\" alt=\"".$basename[1]."\"/>";
							}

							else
								echo "<img src=\"".$this->parseIndexDir()."/images/image_placeholder.png\" style=\"width: 32px; box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none;\" alt=\"".$basename[1]."\"/>";
						}

						else
							echo "<img src=\"".$this->parseIndexDir()."/images/folder.png\" alt=\"".$basename[1]."\"/>";

						echo $basename[1]."</a>\n";
						echo $etag;
					}

					if ($cols > 1)
						echo "\t</tr>\n";
				}

				if ($cols == 1)
					echo "</ul>\n";
				else if ($cols > 1)
					echo "</table>\n";
			}
		}

	function renderCurrentCategories($show_active = true, $cols = 1)
		{
			$this->renderCategories(self::$dir_cache, $show_active, $cols);
		}

	/*! \brief Renders a list of the current image set.
	 *
	 *	Used internally for breadcrumbs, but made public in case of other uses.
	 */
	function renderImageListing($show_active = false)
		{
			$files = self::$img_cache;
			if (is_array($files) && count($files) > 0)
			{
				echo "<ul class=\"tfgallery_categories tfgallery_img_list\">\n";
				foreach ($files as $value)
				{
					$basename = rawurlencode(basename($value));
					if ($this->splitPathInfo(0) == $basename && !$show_active)
						continue;

					echo "\t<li><a href=\"".$this->parseImageLink($value)."\">".$basename."</a></li>\n";
				}
				echo "</ul>\n";
			}
		}

	function renderNextLink()
		{
			$count = count(self::$img_cache);
			if ($count)
			{
				$curr_img_path = $this->getCurrentImagePath();
				$curr_key = array_search($curr_img_path, self::$img_cache);
				++$curr_key;
				if ($curr_key < $count)
					echo "<link href=\"".$this->parseImageLink(self::$img_cache[$curr_key])."\" rel=\"next\"/>\n";
			}
		}

	function renderRootCategories($show_active = true, $cols = 1)
		{
			$this->renderCategories($this->filterPath($this->getSetting("path"), "dirs"),
									$show_active,
									$cols);
		}

	/*! \brief Reserve space in the URL for parsing the URL pattern.
	 *
	 *	If your website is using the same method as TFGallery is (i.e. simple URLs through $_SERVER['PATH_INFO']),
	 *		you may reserve a "slot" in the URL pattern so TFGallery may ignore it when determining paths by URL.
	 *
	 *	\warning
	 *
	 *	This method should be called \em before instantiating any TFGallery object.
	 */
	static function reserveUrlSpace($space)
		{
			self::$space = $space;
		}

	function renderBreadcrumbs($show_menus = true, $separator = "&raquo;")
		{
			$split = $this->splitPathInfo();
			$count = count($split);

			if ($show_menus)
				echo "<div>\n";

			if ($count > 0)
				echo "<a href=\"".$_SERVER['SCRIPT_NAME'].$this->getReservedString()."/\">";
			else
				echo "<span>";

			echo "Index";

			if ($count > 0)
				echo "</a>";
			else
				echo "</span>";

			if ($show_menus)
			{
				echo " $separator\n";
				$this->renderRootCategories();
				echo "</div>\n";
			}

			for ($i = 0; $i < $count; $i++)
			{
				if ($show_menus)
					echo "<div>\n";
				else
					echo " $separator";

				if ($i != $count-1)
					echo "\n<a href=\"".$_SERVER['SCRIPT_NAME'].$this->parseBreadCrumbLink($i, $split)."\">";
				else
					echo "\n<span>";

				echo ($this->getSetting("ucwords") && (!$this->isPathInfoFile() || $i != $count-1)) ? ucwords($split[$i]) : $split[$i];

				if ($i != $count-1)
				{
					echo "</a>";
					// Should be safe to assume this item is a directory.
					$dirs = $this->filterPath($this->getSetting("path").$this->parseBreadCrumbLink($i, $split), "dirs");

					if ($show_menus)
						$this->renderCategories($dirs);
				}

				else
				{
					echo "</span>";
					if (!is_dir($this->getSetting("path").$this->parseBreadCrumbLink($i, $split))
					 && $show_menus)
						$this->renderImageListing();
				}

				if ($show_menus)
				{
					echo " $separator\n";
					echo "</div>\n";
				}
			}
			echo "\n";
		}

	protected function getImageAttributes($image_src, $thumbnail = false)
		{
			if ($thumbnail)
				$image_src = $this->genThumbPath($image_src, true);
						   //dirname($image_src).DIRECTORY_SEPARATOR."thumbs".DIRECTORY_SEPARATOR.basename($image_src);

			$attr = getimagesize($image_src);
			$attr = array('width' => $attr[0], 'height' => $attr[1], 'attr' => $attr[3]);
			$attr['attr'] .= " alt=\"".basename($image_src)."\"";

			return $attr;
		}

	/*!	\brief Render pagination.
	 *
	 *	@param string $element What element to use as the container for the pagination.
	 *	@param int $imagesPerSide How many images (or numbers) to show on each side of the current image.
	 *	@param bool $images Whether to show images instead of text.
	 *
	 *	\note
	 *
	 *	For \p $element block-level elements should be used. Avoid using any elements that require children, e.g. tables.
	 */
	function paginate($element = "div", $imagesPerSide = 2, $images = true)
		{
			echo "<!-- Start Pagination// -->\n";
			echo "<$element class=\"tfg_pagination\">\n";

			$files = self::$img_cache;
			$count = count($files);

			if ($this->isPathInfoFile())
			{
				$curr_img_path = $this->getCurrentImagePath();
				$curr_key = array_search($curr_img_path, $files);
				$offset = $curr_key - $imagesPerSide;
				$offset = ($offset < 0) ? 0 : $offset;

				if ($offset && !empty($files))
				{
					// First image
					if ($images)
					{
						$thumb_attr = $this->getImageAttributes($files[0], true);
						echo "\t<a href=\"".$this->parseImageLink($files[0])."\" rel=\"start\">";
						echo "<img src=\"".$this->genThumbPath($files[0])."\" ".$thumb_attr['attr']."/>";
						echo "</a>\n";
					}

					else
						echo "<a href=\"".$this->parseImageLink($files[0])."\" rel=\"start\">1</a> ";

					echo "&hellip;\n";
				}

				$files2 = array_slice($files, $offset, $imagesPerSide * 2 + 1, true);

				foreach ($files2 as $key => $img)
				{
					if ($curr_key != $key)
					{
						// Images on the sides
						if ($images)
						{
							$next = (($curr_key+1) == $key) ? ' rel="next"' : "";
							$start = ($key == 0) ? ' rel="start"' : "";
							$friend = (!$start && !$next) ? ' rel="friend"' : "";
							$thumb_attr = $this->getImageAttributes($img, true);
							echo "\t<a href=\"".$this->parseImageLink($img)."\"{$next}{$start}{$friend}>";
							echo "<img src=\"".$this->genThumbPath($img)."\" ".$thumb_attr['attr']."/>";
							echo "</a>\n";
						}

						else
							echo "<a href=\"".$this->parseImageLink($img)."\">".($key+1)."</a> ";
					}

					else
					{
						// Active image
						if ($images)
						{
							$thumb_attr = $this->getImageAttributes($files[$key], true);
							echo "\t<img id=\"tfg_active_img\" src=\"".$this->genThumbPath($files[$key])."\" ".$thumb_attr['attr']."/>\n";
						}

						else
							echo "<b>[".($key+1)."]</b> ";
					}
				}

				if (($curr_key + $imagesPerSide) < ($count - 1)
				 && !empty($files))
				{
					// Last image
					echo "&hellip;\n";

					if ($images)
					{
						$thumb_attr = $this->getImageAttributes($files[$count-1], true);
						echo "\t<a href=\"".$this->parseImageLink($files[$count-1])."\">";
						echo "<img src=\"".$this->genThumbPath($files[$count-1])."\" ".$thumb_attr['attr']."/>";
						echo "</a>\n";
					}

					else
						echo "<a href=\"".$this->parseImageLink($files[$count-1])."\">$count</a> ";
				}
			}

			// Not viewing an image
			else
			{
				$limit = $this->getSetting("limit");
				$page = (int) $_GET['page'];
				$page = (--$page < 0) ? 0 : $page;
				$total_pages = $count / $limit;

				for ($i = 0; $i < $total_pages; ++$i)
				{
					if ($page != $i)
						echo "<a href=\"".$_SERVER['REQUEST_URI ']."?page=".($i+1)."\">".($i+1)."</a> ";
					else
						echo "<span>[ ".($i+1)." ]</span> ";
				}
			}

			echo "</$element>\n";
			echo "<!-- //End Pagination -->\n";
		}

	function paginationAvailable()
		{
			if ($this->isPathInfoFile())
				return (bool) (count(self::$img_cache) > 0);
			else
				return (bool) (count(self::$img_cache) > $this->getSetting("limit"));
		}

	function parseIndexDir()
		{
			return str_replace($_SERVER['DOCUMENT_ROOT'], "", __tfgallery_path);
		}

	static function parsePath($path)
		{
			if ($path[0] == ".")
				$path = __tfgallery_path.DIRECTORY_SEPARATOR.$path;
			else if ($path[0] != "/")
				$path = __tfgallery_path.DIRECTORY_SEPARATOR.$path;

			return $path;
		}

	/*! \brief Checks if the current path info points to a file in the file system.
	 */
	function isPathInfoFile()
		{
			$path = $this->getCurrPath().DIRECTORY_SEPARATOR.$this->splitPathInfo("last");

			if ($path == "/")
				return false;

			return is_file($path);
		}

	protected function parseBreadCrumbLink($crumbIndex, $split)
		{
			$path = array_slice($split, 0, $crumbIndex+1);
			return "/".implode("/", $path);
		}

	protected function parseImageLink($path)
		{
			return $_SERVER['SCRIPT_NAME'].$this->getReservedString().str_replace($this->getSetting("path"), "", $path);
		}

	protected function splitPathInfo($index = NULL)
		{
			$split = array_values(array_filter(explode("/", $this->pathinfo)));

			if (is_numeric($index))
				return $split[$index];
			if ($index == "last")
				return $split[count($split)-1];
			else
				return $split;
		}

	static function toBool($var)
		{
			if (is_string($var))
			{
				$var = strtolower($var);
				$true = array("true", "yes");
				$false = array("false", "no");

				if (in_array($var, $true))
					return true;

				else if (in_array($var, $false))
					return false;
			}

			else return (bool) $var;
		}

	static function is_bool($var)
		{
			if (is_numeric($var))
				return (bool) $var;

			else if (is_bool($var))
				return $var;

			else if (is_string($var))
			{
				$bools = array("true", "false", "yes", "no");
				if (in_array(strtolower($var), $bools))
					return true;
			}

			else return false;
		}

	function output()
		{
			include "tfgallery.template.php";
		}

	protected function updateCache($force = false)
		{
			if (!empty(self::$dir_cache)
			 && !empty(self::$img_cache)
			 && !$force)
			 return;

			$cache = $this->filterPath($this->getCurrPath(), "both");
			self::$dir_cache = $cache['dirs'];
			self::$img_cache = $cache['files'];
		}
}

?>
